同一批資料——Day 12 的 48 個特徵、2013 到 2019 年、110 萬列——同樣三個 LightGBM 模型,只換交叉驗證的切法。隨機 k-fold 告訴你最大的那個模型 IC 0.41;真正留出的 2020 到 2026 年,它的 IC 是 0.13,而且是三個裡最差的。
小模型 中模型 大模型
隨機 k-fold(打散所有列) 0.189 0.259 0.412
按日期分組、日期隨機分折 0.182 0.250 0.399
時間區塊,不清洗 0.158 0.161 0.144
時間區塊 + 清洗 4 週 0.159 0.160 0.140
walk-forward(只用過去) 0.154 0.148 0.125
真正的測試期(2020~2026) 0.145 0.144 0.129
上面兩列是錯的,下面三列是對的,最後一列是答案。這篇講為什麼會錯到三倍、錯了會挑到什麼模型、以及怎麼切才對。
特徵是 Day 12 那 48 個,每週抽一天;標籤是「之後 20 日報酬的橫斷面百分位排名」——跟論文一樣預測排名不預測報酬,選股是相對的事,排名也不會被少數暴漲股綁架。2013 到 2019 年當訓練期(341 週),2020 年之後鎖起來當留出測試期(320 週),整篇文章只碰它一次,就是最後那一列。
三個模型只差大小:
小 15 葉、150 棵樹、每葉至少 500 筆
中 63 葉、400 棵樹、每葉至少 200 筆
大 255 葉、800 棵樹、每葉至少 50 筆
評分一律是每週橫斷面的 Spearman IC,取平均。五種切法都是 5 折,都只用訓練期的資料。
隨機 k-fold 的假設是每一列彼此獨立。股票資料在兩個方向上都不獨立。
**橫向:同一天的股票共享同一個市場。**2020 年 3 月 19 日那天,一千多檔股票的標籤一起被大盤決定。把同一天的列一半放訓練、一半放驗證,模型從訓練那一半就學到「這天大家都跌」,驗證那一半自然猜得準。
**縱向:相鄰的週共享同一段報酬。**標籤是 20 個交易日的報酬,每週抽一天,相鄰兩週的標籤窗口重疊 15 天:

相隔 1 週 0.69
相隔 2 週 0.45
相隔 3 週 0.21
相隔 4 週 −0.02 ← 窗口不再重疊
同一檔股票,這週的標籤和下週的標籤相關 0.69。只要下週在訓練集裡,這週的驗證就等於有答案可以抄。
第二種切法「按日期分組」只擋住橫向的漏——同一天的列全在同一折——但日期是隨機分折的,相鄰的週還是會落在不同折。所以它幾乎沒改善:大模型 0.412 變 0.399。很多人以為 GroupKFold by date 就安全了,這個數字說不是。

前兩種驗證資料散在時間軸各處,訓練資料包在它們前後。第三種把時間切成五段,一段驗證、四段訓練——擋住了大部分的漏,但驗證段的前後緊鄰著訓練段,邊界上那幾週還是重疊的。第四種在邊界兩側各清掉 4 週(標籤窗口的長度),這是 de Prado 講的 purging。第五種 walk-forward 只用驗證段之前的資料訓練,最保守,也最接近實際上線時的處境:你永遠只有過去。

三件事。
**漏得越兇,越偏愛大模型。**隨機 k-fold 下,小、中、大三個模型的 IC 是 0.19、0.26、0.41——模型越大越好,因為模型越大越能把重疊的答案背起來。時間區塊之後,三個模型變成 0.16、0.16、0.14,大模型墊底,跟留出期的排序一致。
**清洗的效果在這裡不大。**時間區塊 0.161、加清洗 0.160。五折只有四個交界,交界兩側重疊的週佔比很小;折數越多、抽樣越密(例如每天抽),清洗的差別就會越大。它是便宜的保險,不是這次的主角。
**walk-forward 略低於真實值。**小模型 0.154 對 0.145、大模型 0.125 對 0.129——它用的訓練資料比較少(只有過去),估得保守一點。保守比樂觀好:上線之後只會比驗證少,不會多。
交叉驗證真正的用途是選模型、選超參數。用每一種切法各挑一個「驗證 IC 最高」的模型,再看它在留出期的真實成績:

切法 挑到的模型 留出期 IC
隨機 k-fold 大 0.129
按日期分組 大 0.129
時間區塊 中 0.144
時間區塊 + 清洗 中 0.144
walk-forward 小 0.145
最好的可能 小 0.145
隨機 k-fold 挑到三個裡最差的,walk-forward 挑到最好的。差距 0.016 看起來不大,但那是三個候選的情況;真正調參的時候候選幾十個,漏得越兇的切法,越會把你推向最會背答案的那一個。

留出期六年半的滾動 IC,三個模型糾纏在一起,大模型沒有任何一段領先。隨機 k-fold 說它是 0.41,它從頭到尾都是 0.13。
論文用 2010 年 1 月到 2018 年 6 月當訓練集、k=5 交叉驗證,2018 年 6 月到 2020 年 6 月當測試集。訓練和測試是按時間切的,這一刀是對的;訓練集內部的 5 折怎麼分,論文沒有特別處理時間順序。
這篇的數字說明那個差別值多少:在同樣的訓練期上,用對切法和用錯切法,看到的 IC 可以差三倍,挑出來的模型也不一樣。當年的測試集成績之所以還能看,是因為最後那一刀切對了——測試集真的沒被碰過。交叉驗證漏了,害的是模型選擇,不是最終那個數字。
從這篇開始,第三幕所有模型的訓練照這四條走:
1. 驗證只用 walk-forward:訓練永遠在驗證之前,中間清掉標籤窗口的長度
2. 超參數只在 walk-forward 裡挑;隨機 k-fold 的數字不看
3. 留出期 2020 年之後只碰一次,碰完就是最終成績,不回頭調
4. 成績分行情報告(Day 13):多頭、空頭分開列,全期平均另附
順帶一提:用對切法之後,三個模型在留出期的 IC 是 0.13 到 0.145,Day 13 那個等權六因子分數在同一段是 0.097。模型比等權平均多了將近五成——這是 Day 15 的起點,前提是切法對。
**隨機 k-fold 在股票資料上高估三倍。**大模型驗證 IC 0.41,留出期 0.13。漏的來源有兩個:同一天的股票共享大盤,相鄰週的標籤重疊 15 天(相關 0.69)。
**按日期分組不夠。**它只擋橫向的漏,大模型還是 0.40。要按時間切成區塊,邊界清掉標籤窗口的長度。
**切錯就挑錯。**隨機 k-fold 挑到留出期最差的模型,walk-forward 挑到最好的;漏得越兇,越偏愛最會背答案的大模型。
**walk-forward 略保守,這是好事。**它估的比真實值低一點,因為它跟上線之後的你一樣,只有過去。
從這裡開始,驗證只用 walk-forward,留出期只碰一次。